在了解 reconciliation 前,我們先說說 react 是如何去操作 DOM 的
在 javaScript 中,我們可以利用 createElement
, createTextNode
, appendChild
, removeChild
來操作 DOM 元素,其實 React 也不例外,只不過這部分「操作」的邏輯已經被 react-reconciler 封裝了(實作在 react-dom 裡),只要注入一些 handler 便可以用自定義的方式渲染 React Component,因此我們可以讓 react 使用在非 DOM 的環境(host)下。如果對這部分有興趣,可以參考 awesome-react-renderer 這個 repo,裡面有很多很酷的 renderer。
正因為封裝好 append
, remove
這些 handler,react reconciler 便可以抽象化的方式去操作 Component,把邏輯專注在 diff 上面。
從一個 Component 的角度來看,React 的渲染流程大致如下:
|----------| |---------| |--------|
| render | ---> | prepare | ---> | commit |
|----------| |---------| |--------|
|___ user __| |_ react-reconciler <== react-dom __|
可以發現從進 render() 之後的過程全都交由 reconciler 來進行操作
我們可以注意到我旁邊寫了一個 <== react-dom
,正如同上面所寫 react-reconciler
負責了 render 後的兩個環節(prepare
, commit
)。但實際上他只是負責 invoke 外面 host 丟進來的 config 而已,因此這裡實際中呼叫的都是 react-dom
注入進來的 handler。
另外,我們從 react-dom 的 source code 中可以發現會有 diff 做在 prepareUpdate
(其中一個 host config) 中,他會 return diff 的結果,交由 commitUpdate
來做 DOM 的渲染。
/**
* excerpt from react-dom source code
*/
export function prepareUpdate(
domElement: Instance,
type: string,
oldProps: Props,
newProps: Props,
rootContainerInstance: Container,
hostContext: HostContext,
): null | Array<mixed> {
...
return diffProperties(
domElement,
type,
oldProps,
newProps,
rootContainerInstance,
);
}
reconciler 使用的是 heuristic 的算法,建立在以下兩個假設上:
也正因為這些前提,才可以利用較簡單的規則來比較 vDOM node,並決定要進行何種操作。
至於詳細的規則,大家可以直接看 ReactJS 官方的 Docs,這裡就不贅述了
其實我覺得上面的 diff 感覺和更上面 prepare 內的 diff 不大一樣
看起來一個是基於 vDOM tree,一個是基於 component
越想越不明白了,明天有空再來研究一下 reconciler 的 source code 好了
希望下一篇能夠解惑 (`・ω・´)